package batch

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/astaxie/beego/orm"
	"github.com/golang/glog"
	uuid "github.com/satori/go.uuid"
	"k8s.io/client-go/kubernetes"

	authapi "eccgateway/pkg/api/auth"
	apibatch "eccgateway/pkg/api/batch"
	"eccgateway/pkg/api/dataselect"
	"eccgateway/pkg/config"
	dbbatch "eccgateway/pkg/db/batch"
	"eccgateway/pkg/util"
)

const (
	//status
	Running        = "running"
	Pending        = "pending"
	Timeout        = "timeout"
	Success        = "success"
	Stopping       = "stopping"
	Stop           = "stop"
	Failure        = "failure"
	PartialSuccess = "partialsuccess"
)

var allowStopStatus = []string{
	Pending,
	Running,
}

var allowStartStatus = []string{
	Timeout,
	Stop,
	Failure,
	PartialSuccess,
}

var allowDeleteStatus = []string{
	Timeout,
	Stop,
	Success,
	Failure,
	PartialSuccess,
}

type BatchService struct {
	workerNum int

	assignChan []chan *apibatch.Batch
	assigned   []string
	stopChan   []chan string

	kubeClient *kubernetes.Clientset
}

func NewBatchService() BatchService {
	batchWorkerNum, _ := config.GetConfig().Int("batchWorkerNum")
	glog.Infof("batch worker num: %v", batchWorkerNum)

	s := BatchService{
		workerNum:  batchWorkerNum,
		stopChan:   make([]chan string, batchWorkerNum),
		assignChan: make([]chan *apibatch.Batch, batchWorkerNum),
		assigned:   make([]string, batchWorkerNum),
	}
	util.RegisterFunc(s.Start)
	return s
}

func (s *BatchService) Create(userInfo authapi.RespToken, req apibatch.CreateBatchWorkReq) (apibatch.Batch, error) {
	template, err := json.Marshal(req.Template)
	if err != nil {
		glog.Errorf("marshal batch work[%v]'s template for user[%v/%v] failed, err: %v", req.Name, userInfo.Name, userInfo.UserID, err)
		return apibatch.Batch{}, err
	}
	res := apibatch.Batch{
		ID:              uuid.NewV4().String(),
		UserID:          userInfo.UserID,
		Username:        userInfo.Name,
		Name:            req.Name,
		Type:            req.Type,
		Status:          Pending,
		CreateTimestamp: time.Now(),
		StartTimestamp:  time.Unix(0, 0),
		EndTimestamp:    time.Unix(0, 0),
		Success:         []apibatch.Detail{},
		Failure:         []apibatch.Detail{},
		Pending:         transformList2Pending(req.List, req.Name, req.Type),
		Template:        string(template),
	}
	batchInDB := transformAPI2DB(res)
	batchInDB, err = dbbatch.CreateBatch(orm.NewOrm(), batchInDB)
	if err != nil {
		glog.Errorf("create batch work[%v] for user[%v/%v] in db failed, err: %v", req.Name, userInfo.Name, userInfo.UserID, err)
		return apibatch.Batch{}, err
	}
	res = transformDB2API(batchInDB)
	return res, nil
}

func (s *BatchService) GetBatchWorkByUserIDAndName(userID string, name string) (apibatch.Batch, error) {
	batchInDB, err := dbbatch.GetBatchByNameAndUserID(orm.NewOrm(), name, userID)
	if err == orm.ErrNoRows {
		glog.Errorf("can't find batch work[%v] for user[%v] in db", name, userID)
		return apibatch.Batch{}, fmt.Errorf("batch work[%v] is not found", name)
	}
	if err != nil {
		glog.Errorf("get batch work[%v] for user[%v] in db failed, err: %v", name, userID, err)
		return apibatch.Batch{}, err
	}
	res := transformDB2API(batchInDB)
	return res, nil
}

func (s *BatchService) ListBatchWorkByUserID(userIDs []string, query *dataselect.DataSelectQuery) ([]apibatch.Batch, int64, error) {
	batchInDBs, nums, err := dbbatch.ListBatchByUserID(orm.NewOrm(), userIDs, query)
	if err == orm.ErrNoRows {
		glog.Infof("can't list batch work for user[%v] in db", strings.Join(userIDs, ","))
		return []apibatch.Batch{}, 0, nil
	}
	if err != nil {
		glog.Errorf("can't list batch work for user[%v] in db, err: %v", strings.Join(userIDs, ","), err)
		return []apibatch.Batch{}, 0, err
	}
	res := transformDBs2APIs(batchInDBs)
	return res, nums, nil
}

func (c *BatchService) StopBatchWork(userID string, name string) (apibatch.Batch, error) {
	res, err := c.GetBatchWorkByUserIDAndName(userID, name)
	if err != nil {
		return apibatch.Batch{}, err
	}

	isAllowStop := false
	for _, v := range allowStopStatus {
		if v == res.Status {
			isAllowStop = true
			break
		}
	}
	if !isAllowStop {
		glog.Errorf("can't stop batch work[%v], the status[%v] doesn't allow", name, res.Status)
		return apibatch.Batch{}, fmt.Errorf("the batch work[%v]'s status[%v] doesn't allow to be stopped, allow stop status: [%v]", name, res.Status, strings.Join(allowStopStatus, ","))
	}
	res.Status = Stopping // waiting backend to stop

	batchInDB := transformAPI2DB(res)
	err = dbbatch.UpdateBatch(orm.NewOrm(), batchInDB)
	if err != nil {
		glog.Errorf("update batch work[%v] for user[%v] in db to stop failed, err: %v", name, userID, err)
		return apibatch.Batch{}, err
	}
	return res, nil
}

func (c *BatchService) StartBatchWork(userID string, name string) (apibatch.Batch, error) {
	res, err := c.GetBatchWorkByUserIDAndName(userID, name)
	if err != nil {
		return apibatch.Batch{}, err
	}

	isAllowStart := false
	for _, v := range allowStartStatus {
		if v == res.Status {
			isAllowStart = true
			break
		}
	}
	if !isAllowStart {
		glog.Errorf("can't start batch work[%v], the status[%v] doesn't allow", name, res.Status)
		return apibatch.Batch{}, fmt.Errorf("the batch work[%v]'s status[%v] doesn't allow to be started, allow start status: [%v]", name, res.Status, strings.Join(allowStartStatus, ","))
	}
	res.Status = Pending // waiting backend to start

	// set failure to retry
	t := time.Now()
	m := make(map[string]bool, len(res.Failure))
	for _, v := range res.Failure {
		if m[v.Deployment] {
			continue
		}
		m[v.Deployment] = true
		res.Pending = append(res.Pending, apibatch.Detail{
			Cluster:    v.Cluster,
			Deployment: v.Deployment,
			Node:       v.Node,
			Namespace:  v.Namespace,
			Timestamp:  t,
		})
	}
	res.PendingCount = len(res.Pending)
	res.FailureCount = 0
	res.Failure = []apibatch.Detail{}

	batchInDB := transformAPI2DB(res)
	err = dbbatch.UpdateBatch(orm.NewOrm(), batchInDB)
	if err != nil {
		glog.Errorf("update batch work[%v] for user[%v] in db to start failed, err: %v", name, userID, err)
		return apibatch.Batch{}, err
	}

	return res, nil
}

func (c *BatchService) DeleteBatchWork(userID string, name string) (apibatch.Batch, error) {
	res, err := c.GetBatchWorkByUserIDAndName(userID, name)
	if err != nil {
		return apibatch.Batch{}, err
	}

	isAllowDelete := false
	for _, v := range allowDeleteStatus {
		if v == res.Status {
			isAllowDelete = true
			break
		}
	}
	if !isAllowDelete {
		glog.Errorf("can't delete batch work[%v], the status[%v] doesn't allow", name, res.Status)
		return apibatch.Batch{}, fmt.Errorf("the batch work[%v]'s status[%v] doesn't allow to be deleted, allow delete status: [%v]", name, res.Status, strings.Join(allowDeleteStatus, ","))
	}

	batchInDB := transformAPI2DB(res)
	err = dbbatch.DeleteBatch(orm.NewOrm(), batchInDB)
	if err != nil {
		glog.Errorf("delete batch work[%v] for user[%v] in db failed, err: %v", name, userID, err)
		return apibatch.Batch{}, err
	}

	return res, nil
}
